Stirling-PDF
👋 Présentation
Le logiciel Stirling PDF est accessible via votre navigateur web et présente les fonctionnalités suivantes (liste non exhaustive) :
- Interface graphique interactive complète pour fusionner/diviser/faire pivoter/déplacer des pages et des PDF complets.
- Possibilité de diviser les PDF en plusieurs fichiers à partir des numéros de page spécifiés ou d’extraire toutes les pages en tant que fichiers individuels.
- Fusionner plusieurs PDF ensemble en un seul fichier.
- Ajouter/Générer des signatures.
- Auto Split PDF (Avec séparateurs de page numérisés physiquement)
- Supprimer les pages vierges.
- Convertir PDF en Word/Powerpoint/Autres (à partir de fichiers non scannés, tout comme les logiciels payant du commerce)
- Reconnaissance optique des caractères avec l’OCR (Optical Character Recognition).
- Authentification possible intégrée ou en mode SSO.
- API
Liste complète des fonctionnalités
L'application est sous licence GPL-3.0.
J'installerai le logciciel sur Docker Swarm, mais il sera relativement facile de le basculer sur Docker.
Les briques logicielles suivantes seront utilisées :
- Traefik comme reverse-proxy avec le middleware Crowdsec.
- Authelia comme solution IAM qui permettra de gérer l'accès et l'authentification sur Stirling-PDF.
Il faudra également déclarer chez votre registrar le nom de domaine utilisé pour l'application.
Introduction au protocole OIDC
OIDC a été développé par l’OpenID Foundation, qui comprend des sociétés comme Google et Microsoft. Alors qu’OAuth 2.0 est un protocole d'autorisation, OIDC est un protocole d'authentification d'identité qui sert à vérifier l'identité d'un utilisateur d'un service client. Il permet d’ajouter une surcouche d’authentification à OAuth 2.0, tout en donnant la possibilité de faire de la fédération d’identité. Cela permet notamment d’offrir à l’utilisateur la possibilité d’utiliser plusieurs services, en se connectant une seule fois auprès d’un tiers de confiance, grâce au SSO.
Voici les étapes du processus OIDC :
- L'utilisateur accède à l'application cliente : L'utilisateur tente de se connecter à une application cliente (Client).
- Redirection vers le fournisseur d'identité (IdP) : Le client redirige l'utilisateur vers le fournisseur d'identité (IdP) avec une demande d'authentification.
- Authentification de l'utilisateur : L'utilisateur se connecte auprès du fournisseur d'identité (IdP), généralement en saisissant ses identifiants.
- Consentement de l'utilisateur : L'IdP peut demander à l'utilisateur de consentir à partager certaines informations avec le client.
- Le fournisseur d'identité émet des tokens : - Après authentification et consentement, l'IdP redirige l'utilisateur vers le client avec un code d'autorisation. - Le client échange ce code d'autorisation contre des tokens (ID token, access token et potentiellement un refresh token) auprès de l'IdP.
- Le client utilise les tokens : - Le client utilise l'ID token pour obtenir des informations sur l'utilisateur (claims). - L'access token peut être utilisé pour accéder aux ressources protégées par des API.
Configuration d'Authelia
Génération des secrets
Nous utiliserons la stack Authelia/LLDAP pour authentifier les utilisateurs souhaitant utiliser Stirling-PDF. Cette stack est documenté ici : Stack AUthelia/LLDAP
olivier@ds06:/mnt/nfsdatas/stack-sso$ tree -L 3
.
├── config
│ ├── authelia
│ │ └── configuration.yml
│ └── lldap
│ ├── lldap_config.toml
│ ├── private_key
│ ├── ssl
│ └── users.db
├── data
│ ├── authelia
│ │ └── db.sqlite3
│ └── redis
│ └── dump.rdb
├── docker-stack.yml
├── env
│ ├── container-vars-authelia.env
│ └── container-vars-lldap.env
├── log
│ └── authelia
│ └── authelia.log
└── secrets
├── authelia
│ ├── AUTHENTICATION_BACKEND_LDAP_PASSWORD
│ ├── JWT_SECRET
│ ├── NOTIFIER_SMTP_PASSWORD
│ ├── SESSION_SECRET
│ └── STORAGE_ENCRYPTION_KEY
└── lldap
├── JWT_SECRET
└── LDAP_USER_PASS
Avant de configurer le provider et le client OIDC, nous allons renseigner les secrets afin de protéger les informations sensibles.
Pour commencer il faut créer le secret HMAC.
HMAC et JWT
Le HMAC dans OIDC est utilisé pour signer les tokens JWT, assurant ainsi l'intégrité et l'authenticité des messages. C'est une méthode efficace et sécurisée lorsqu'une clé secrète partagée peut être maintenue en toute sécurité entre les parties impliquées. Le JWT est un élément clé d'OpenID Connect, utilisé pour transmettre de manière sécurisée et compacte des informations sur l'utilisateur et l'authentification.
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker run authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric
Random Value: R9A77AAq1G0Nz85dWQedoCEHSY98pvqGnq0vuMOhAYu0gnQa9adaazOHIW0K0mJP
# Copier/Coller la valeur dans secrets/authelia/IDENTITY_PROVIDERS_OIDC_HMAC_SECRET
Il faut ensuite générer le ClientSecret. Le Client Secret est une sorte de mot de passe applicatif. Conservez la sortie des commandes suivantes.
# ClientID
olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker run authelia/authelia:latest authelia crypto rand --length 64 --charset alphanumeric
Random Value: mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv
# ClientSecret
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker run authelia/authelia:latest authelia crypto hash generate pbkdf2 --variant sha512 --random --random.length 72 --random.charset rfc3986
# Affichage du mot de passe en clair que nous renseignerons dans la configuration OIDC de Stirling-PDF
Random Password: buHsi7V0xU_rRuKlJGCcpUAgeVi79GEbzMax~qCfi70_BhRubUrQOmbNDRH2_rqJPSWWojUc
# Affichage du mot de passe chiffré que nous renseignerons dans la configuration OIDC sur Authelia
Digest: $pbkdf2-sha512$310000$Z5Tz88B7XZKZEzgT..v6pw$aTu9p/.hfrhf/v0Z.fAu9zH4KTKzJCwheIMDr0uY5jhcHymDyYCFKMFoNJ.5Qj7fURGoenIhog1WPrI6ddpW7w
Enfin il faut générer un certificat auto-signé qui sera utilisé par JWKS.
JWKS
Le JWKS est essentiel pour la sécurité dans les systèmes OIDC et JWT. Il permet aux clients de vérifier l'authenticité et l'intégrité des tokens en fournissant un moyen standardisé de publier et de récupérer les clés publiques utilisées pour la signature des tokens. Lorsqu'un client reçoit un JWT, il doit vérifier la signature du token pour s'assurer qu'il a été émis par un fournisseur de confiance et qu'il n'a pas été altéré. Le client récupère le document JWKS du fournisseur d'identité et utilise la clé publique appropriée pour vérifier la signature du JWT.
olivier@ds06:/mnt/nfsdatas/stack-sso$ cd secrets/authelia
olivier@ds06:/mnt/nfsdatas/stack-sso/secrets/authelia$ docker run -u "$(id -u):$(id -g)" -v "$(pwd)":/keys authelia/authelia:latest authelia crypto certificate rsa -b 3072 generate --common-name authelia.idp --directory /keys
Generating Certificate
Serial: 53d1088937434bccd33159d98245a6c9
Signed By:
Self-Signed
Subject:
Common Name: authelia.idp, Organization: [Authelia], Organizational Unit: []
Country: [], Province: [], Street Address: [], Postal Code: [], Locality: []
Properties:
Not Before: 2024-06-22T13:57:14Z, Not After: 2025-06-22T13:57:14Z
CA: false, CSR: false, Signature Algorithm: SHA256-RSA, Public Key Algorithm: RSA, Bits: 3072
Subject Alternative Names:
Output Paths:
Directory: /keys
Private Key: private.pem
Certificate: public.crt
Configuration du provider OIDC
# À ajouter à la fin du fichier config/authelia/configuration.yml
(...)
identity_providers:
oidc:
jwks:
- key: {{ secret "/secrets/private.pem" | mindent 10 "|" | msquote }}
certificate_chain: |
-----BEGIN CERTIFICATE-----
MIIEEzCCAnugAwIBAgIQU9EIiTdDS8zTMVnZgkWmyTANBgkqhkiG9w0BAQsFADAq
MREwDwYDVQQKEwhBdXRoZWxpYTEVMBMGA1UEAxMMYXV0aGVsaWEuaWRwMB4XDTI0
MDYyMjEzNTcxNFoXDTI1MDYyMjEzNTcxNFowKjERMA8GA1UEChMIQXV0aGVsaWEx
FTATBgNVBAMTDGF1JHgsdnbjyGFDCxchgfdSxhfJKoZIhvcNAQEBBQADggGPADCC
AYoCggGBAKyI1TciWM2qMjp2ahkeRhV8rmPIUfG0wpn9HPkqlHBc052yK/C2vd1G
a/yQf7KWA16uiDasEynk3mvX6wGtNvwX4qe1t91Zgk/pC1NDl7zzUIgnAs7yok38
L3XCqnYrmJjPqj9ACfBLv/2q/D7Z7ft79CEADWUCf74hY/Ksy5XN3Doju1Ye2RyR
zCGn0fEE944juHRO4U8GHjIeoHuq8Qvm+t7RJzVG4BmZcvOA/VY+CWmeB7v1Q2mK
VE5XklZJpSVjv392H+n4hzbAILW0dP499mOiGDXU8nlXz1UxQfG6eiqyVDKCXZ8v
7a7epQL3yT3HZOE8d2SmbmlksdfoijHUGf+PLlx7MaCqcnb6doqWWu7RuIT1c141
vCuyC3mhktGRmEldh0y0wnopy9wDsGP4ska4jJX5oNOEQaP9+HpF364TgC0cUvOs
0EflN0+ERlup/gbXULVLH5bu3VwdLEzG9UXHtm9G9FWEa9f2/1KqbJ+v77wrKjdj
mUxgtsy8MwIDAQABozUwMzAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYB
BQUHAwEwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAYEAJH8CjMw8AkR+
bCqu7I9xOF97JhOcG6OyPyL5TOHEtHtL3rOZ/0xwEp8ceC9R3vYMB/vH91OawJte
lEQhVpTSPHZLTE/9Fo7dz4PL0TuzFpXlMC36ujOrGRQHOCLsaEoTN372d7qKeu7e
yFtfVtqHVZMN/cBALspSK/oz5v6EeRBWC7mObBUEOGKIp1JopEErR6TGM5d4e6di
XfdAcMAxyJ3qhCx+4o4VxQUYaTUlzAjHdnDwU590znk9FY16kcS4ibJ13B/lqCxY
yXGuirnJTdidHLKUIOUnshdsy+oohJHGHGFuTdHTsAZErsfOfYaYU+WuhPRIDT6z
0q344WkNwvDYnnth09q4K3W9Bjjpm1b8AtLZl2Q4TNtDMs6phKUtZMdNVSRgRvxb
oNcSObLjXwf7HSSvcFWKpVLKYVCK8ArIWNLKkeIIDuYWdzBmb01LWi0Jdo6HKPtR
+8S8dTf2dc/ehNjE4MxzuUItBmsGFk/3R24OKf5qajPAd1lFP6MK
-----END CERTIFICATE-----
lifespans.access_token: 1h
lifespans.authorize_code: 1m
lifespans.id_token: 1h
lifespans.refresh_token: 90m
enable_client_debug_messages: false
clients:
- client_id: 'mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv'
client_name: 'Stirling-PDF'
client_secret: '$pbkdf2-sha512$310000$Z5Tz88B7XZKZEzgT..v6pw$aTu9p/.hfrhf/v0Z.fAu9zH4KTKzJCwheIMDr0uY5jhcHymDyYCFKMFoNJ.5Qj7fURGoenIhog1WPrI6ddpW7w'
public: false
authorization_policy: 'one_factor'
redirect_uris:
- 'https://pdf.dev.quercylibre.fr/login/oauth2/code/oidc'
scopes:
- 'openid'
- 'email'
- 'profile'
requested_audience_mode: 'explicit'
consent_mode: 'auto'
pre_configured_consent_duration: '1y'
Il faut déclarer la variable HMAC.
# Ajouter ces 2 lignes dans le fichier
(...)
X_AUTHELIA_CONFIG_FILTERS=template # Important pour importer la clé privée générée précédemment secrets/private.pem
AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE=/secrets/IDENTITY_PROVIDERS_OIDC_HMAC_SECRET
Il ne reste plus qu'à relancer la stack sso et vérifier dans les logs que tout est ok.
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker stack rm sso
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker stack deploy -c docker-stack.yml -d sso
# Il faut patienter environ 1 minute le temps que LLDAP soit lancé
olivier@ds06:/mnt/nfsdatas/stack-sso$ docker service logs sso_authelia -f
olivier@ds06:/mnt/nfsdatas/stack-sso$ tree -L 3
.
├── config
│ ├── authelia
│ │ └── configuration.yml
│ └── lldap
│ ├── lldap_config.toml
│ ├── private_key
│ ├── ssl
│ └── users.db
├── data
│ ├── authelia
│ │ └── db.sqlite3
│ └── redis
│ └── dump.rdb
├── docker-stack.yml
├── env
│ ├── container-vars-authelia.env
│ └── container-vars-lldap.env
├── log
│ └── authelia
│ └── authelia.log
└── secrets
├── authelia
│ ├── AUTHENTICATION_BACKEND_LDAP_PASSWORD
│ ├── IDENTITY_PROVIDERS_OIDC_HMAC_SECRET
│ ├── JWT_SECRET
│ ├── NOTIFIER_SMTP_PASSWORD
│ ├── private.pem
│ ├── public.crt
│ ├── SESSION_SECRET
│ └── STORAGE_ENCRYPTION_KEY
└── lldap
├── JWT_SECRET
└── LDAP_USER_PASS
Configuration de Stirling-PDF
olivier@ds06:/mnt/nfsdatas$ mkdir -p stirling-pdf/config
olivier@ds06:/mnt/nfsdatas$ sudo chown -R olivier: stirling-pdf/config
olivier@ds06:/mnt/nfsdatas$ cd stirling-pdf
Il faut créer le fichier de configuration du service.
services:
stirling-pdf:
image: frooodle/s-pdf:latest
networks:
- web
volumes:
- ./config:/configs:rw
# TODO : tester la reconnaissance OCR sur RPI
# - /usr/share/tesseract-ocr/4.00/tessdata:/usr/share/tesseract-ocr/4.00/tessdata
environment:
DOCKER_ENABLE_SECURITY: "true"
SECURITY_ENABLELOGIN: "true"
SECURITY_OAUTH2_ENABLED: "true"
SECURITY_OAUTH2_AUTOCREATEUSER: "true" # This is set to true to allow auto-creation of non-existing users in Stirling-PDF
SECURITY_OAUTH2_ISSUER: "https://a.dev.quercylibre.fr"
SECURITY_OAUTH2_CLIENTID: "mED8rrPupGTtrKEVcbNlRPxkNOnNTUHelFtZQuiFHRQgxDchfGy3Nn0SRmPeSGuv" # Client ID from your provider
# Pas de possibilité d'utiliser Docker Secrets. Le ClientSecret est tout de même masqué dans le logs.
SECURITY_OAUTH2_CLIENTSECRET: "buHsi7V0xU_rRuKlJGCcpUAgeVi79GEbzMax~qCfi70_BhRubUrQOmbNDRH2_rqJPSWWojUc" # Client Secret from your provider
SECURITY_OAUTH2_SCOPES: "openid,profile,email" # Expected OAuth2 Scope
SECURITY_OAUTH2_USEASUSERNAME: "preferred_username" # Utilisation du username. Par défaut, c'est l'email. L'attribut "name" pour le "display name" génère un bug.
SECURITY_OAUTH2_PROVIDER: "authelia" # Set this to your OAuth provider's name, e.g., 'google' or 'keycloak'
PUID: 1001
PGID: 1001
SYSTEM_MAXFILESIZE: "50"
SYSTEM_DEFAULTLOCALE: fr-FR
LANGS: fr-FR
UI_APPNAME: Boite à outils PDF
UI_HOMEDESCRIPTION: Votre guichet unique auto-hébergé pour travailler sur vos PDF.
UI_APPNAVBARNAME: Boite à outils PDF
deploy:
restart_policy:
condition: on-failure
replicas: 1
resources:
limits:
memory: 2G
placement:
constraints:
- node.hostname == ds06 # C'est le RPI 4.
labels:
- "traefik.enable=true"
- "traefik.docker.network=web"
- "traefik.http.routers.pdf.rule=Host(`pdf.dev.quercylibre.fr`)"
- "traefik.http.routers.pdf.tls=true"
- "traefik.http.routers.pdf.tls.certresolver=letsencrypt"
- "traefik.http.routers.pdf.middlewares=crowdsec@file"
- "traefik.http.services.pdf-service.loadbalancer.server.port=8080"
- "diun.enable=true"
networks:
web:
external: true
On lance le service.
olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker stack deploy -c docker-stack.yml -d pdf
# Suivi du chargement du service
olivier@ds06:/mnt/nfsdatas/stirling-pdf$ docker service logs pdf_stirling-pdf -f
🚀 Connexion à Stirling-PDF
Connectez-vous sur l'url déclarée puis cliquez sur "Se connecter via l'authentification unique"
Cliquez sur Authelia.
Authentifiez-vous sur Authelia.
Cochez la case "Se souvenir du consentement" puis cliquez sur Accepter.
Vous êtes alors redirigé vers Stirling-PDF
Cliquez sur l'engrenage en haut à droite. Depuis ce panneau vous pouvez vous déconnecter ou bien accéder aux paramètres du compte.
Si vous cliquez sur "Paramètres du compte", vous avez alors accès à l'option pour paramétrer la clé API.
🏁 Conclusion
J'ai testé quelques fonctionnalités comme l'ajout d'un filigrane, la division d'un pdf en plusieurs PDF... Et ça fonctionne 🤩
Au niveau de la prise en charge du SSO, il y a quelques bugs comme la déconnexion ou la non prise en compte du Display Name.